////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-2018 Qualcomm Technologies, Inc.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file camxhal3queue.cpp
/// @brief Implements HAL3 Blocking Queue
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "camxdebugprint.h"
#include "camxmem.h"
#include "camxhal3queue.h"
#include "camxutils.h"

CAMX_NAMESPACE_BEGIN

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Type definitions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief A metadata slot descriptor for an element in the queue
struct SlotDescriptor
{
    BOOL    inUse;      ///< is this queue slot in use by the client (i.e. cannot reuse until it's released by the client)
    UINT32  index;      ///< position of this slot inside m_pDataBlob
    UINT32  signature;  ///< match signature when pData comes back in release
    BYTE*   pData;      ///< actual client data of this slot
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::Create
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HAL3Queue* HAL3Queue::Create(
    SIZE_T      elementSize,
    UINT32      maxElements,
    CreatedAs   createType)
{
    CamxResult result          = CamxResultEFailed;
    HAL3Queue* pLocalInstance  = NULL;

    CAMX_ASSERT(0 != elementSize);
    CAMX_ASSERT(0 != maxElements);

    if ((0 == elementSize) || (0 == maxElements))
    {
        return NULL;
    }

    pLocalInstance = CAMX_NEW HAL3Queue(elementSize, maxElements);

    if (NULL != pLocalInstance)
    {
        result = pLocalInstance->Initialize(createType);

        if (CamxResultSuccess != result)
        {
            CAMX_DELETE pLocalInstance;
            pLocalInstance = NULL;
        }
    }

    return pLocalInstance;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::Destroy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID HAL3Queue::Destroy()
{
    CAMX_DELETE this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::HAL3Queue
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HAL3Queue::HAL3Queue(
    SIZE_T  elementSize,
    UINT32  maxElements)
    : m_perSlotDataSize(elementSize)
    , m_maxSlots(maxElements)
{
    m_perSlotSize = m_perSlotDataSize + sizeof(SlotDescriptor);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::~HAL3Queue
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HAL3Queue::~HAL3Queue()
{
    if (NULL != m_pDataBlob)
    {
        CAMX_FREE(m_pDataBlob);
        m_pDataBlob = NULL;
    }

    if (NULL != m_pWaitFlush)
    {
        m_pWaitFlush->Destroy();
        m_pWaitFlush = NULL;
    }

    if (NULL != m_pWaitEmpty)
    {
        m_pWaitEmpty->Destroy();
        m_pWaitEmpty = NULL;
    }

    if (NULL != m_pWaitFull)
    {
        m_pWaitFull->Destroy();
        m_pWaitFull = NULL;
    }

    if (NULL != m_pQueueLock)
    {
        m_pQueueLock->Destroy();
        m_pQueueLock = NULL;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::Initialize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult HAL3Queue::Initialize(
    CreatedAs createType)
{
    CamxResult      result    = CamxResultEFailed;
    UINT            slotIndex = 0;
    SIZE_T          size      = m_perSlotSize * m_maxSlots;
    SlotDescriptor* pDesc     = NULL;

    m_pDataBlob = static_cast<BYTE*>(CAMX_CALLOC_ALIGNED(size, 4));

    if (NULL != m_pDataBlob)
    {
        result = CamxResultSuccess;

        BOOL slotInUse = FALSE;

        switch (createType)
        {
            case CreatedAs::Full:   // Q is created full
                slotInUse = TRUE;
                break;

            case CreatedAs::Empty:  // Q is created empty
            default:
                break;
        }

        for (slotIndex = 0; slotIndex < m_maxSlots; slotIndex++)
        {
            pDesc = GetSlotDescriptor(slotIndex);

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            ///                     pDesc
            ///                       | pDesc->pData
            ///                       | |
            ///  __________ _ ________V_V________ _ ________ _ ________ _ ________ _ _________
            /// | |        | |        | |        | |        | |        | |        | |
            /// | |        | |        | |        | |        | |        | |        | |
            /// | |        | |        | |        | |        | |        | |        | |
            /// |_|________|_|________|_|________|_|________|_|________|_|________|_|_________
            /// {_ _    _ _}           ^
            ///      \ /               |
            ///       |                |
            /// m_perSlotSize   struct SlotDescriptor
            ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            pDesc->pData     = m_pDataBlob + (slotIndex * m_perSlotSize) + sizeof(SlotDescriptor);
            pDesc->inUse     = slotInUse;
            pDesc->index     = slotIndex;
            pDesc->signature = CamxCanary;
        }
    }

    if (CamxResultSuccess == result)
    {
        m_pQueueLock  = Mutex::Create("Hal3Queue");
        m_pWaitEmpty  = Condition::Create("Hal3Queue Empty");
        m_pWaitFull   = Condition::Create("Hal3Queue Full");
        m_pWaitFlush  = Condition::Create("Hal3Queue Flush");

        if ((NULL == m_pQueueLock) || (NULL == m_pWaitEmpty) || (NULL == m_pWaitFull) || (NULL == m_pWaitFlush))
        {
            result = CamxResultEFailed;
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::CanEnqueue
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL HAL3Queue::CanEnqueue() const
{
    // m_tail always points to the slot where the inordered enqueue is to happen next. So if m_tail points to a slot which is
    // currently in use, it means we cannot enqueue anymore - and thats because we enforce an inordered enqueue/dequeue
    BOOL canEnqueue = ((TRUE == GetSlotDescriptor(m_tail)->inUse) ? FALSE : TRUE);

    return canEnqueue;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::IsEmpty
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL HAL3Queue::IsEmpty() const
{
    // m_head always points to the slot that will be dequeued and m_tail points to a slot in which the next enqueue will happen
    // So if both of them point to slot that is currently not-in-use, it means the queue is empty
    BOOL isEmpty = (((m_head == m_tail) && (FALSE == IsSlotInUse(m_head))) ? TRUE : FALSE);

    return isEmpty;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::ReleaseCore
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID HAL3Queue::ReleaseCore(
    VOID* pData)
{
    SlotDescriptor* pReleasedDesc = reinterpret_cast<SlotDescriptor*>(static_cast<BYTE*>(pData) - sizeof(SlotDescriptor));

    CAMX_ASSERT(CamxCanary == pReleasedDesc->signature);

    pReleasedDesc->inUse = FALSE;

    // After a release, if we can enqueue now, signal the waiting Enqueues
    if (TRUE == CanEnqueue())
    {
        m_pWaitFull->Signal();
    }

    // After a release, If queue becomes empty, signal anyone waiting for the queue to be empty
    if (TRUE == IsEmpty())
    {
        m_pWaitEmpty->Signal();
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::EnqueueCore
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult HAL3Queue::EnqueueCore(
    VOID* pData)
{
    CamxResult result = CamxResultEFailed;

    if (TRUE == CanEnqueue())
    {
        SlotDescriptor* pDesc = NULL;
        // Always enqueue at tail
        pDesc        = GetSlotDescriptor(m_tail);
        pDesc->inUse = TRUE;

        SessionCaptureRequest* pSessionRequestSrc = reinterpret_cast<SessionCaptureRequest*>(pData);
        SessionCaptureRequest* pSessionRequestDst = reinterpret_cast<SessionCaptureRequest*>(pDesc->pData);

        pSessionRequestDst->numRequests = pSessionRequestSrc->numRequests;
        Utils::Memcpy(&pSessionRequestDst->requests[0], &pSessionRequestSrc->requests[0],
                      sizeof(CaptureRequest) * pSessionRequestSrc->numRequests);

        m_tail = ((m_tail + 1) % m_maxSlots);

        // Enqueue always signals the waiting Dequeues
        m_pWaitEmpty->Signal();

        result = CamxResultSuccess;
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::DequeueCore
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID* HAL3Queue::DequeueCore()
{
    VOID* pReturnedData = NULL;

    if (FALSE == IsEmpty())
    {
        // Always dequeue from the head
        SlotDescriptor* pDesc = GetSlotDescriptor(m_head);

        pReturnedData = pDesc->pData;

        m_head = ((m_head + 1) % m_maxSlots);

        CAMX_ASSERT(TRUE == pDesc->inUse);
    }

    return pReturnedData;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::Enqueue
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult HAL3Queue::Enqueue(
    VOID* pData)
{
    CamxResult result = CamxResultSuccess;

    CAMX_ASSERT(NULL != pData);

    if (NULL == pData)
    {
        return CamxResultEFailed;
    }

    m_pQueueLock->Lock();

    result = EnqueueCore(pData);

    m_pQueueLock->Unlock();

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::Dequeue
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID* HAL3Queue::Dequeue()
{
    VOID* pData = NULL;

    m_pQueueLock->Lock();

    pData = DequeueCore();

    m_pQueueLock->Unlock();

    if (TRUE == IsEmpty())
    {
        m_pWaitFlush->Signal();
    }

    return pData;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::Peek
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL HAL3Queue::Peek(
    VOID*  pCopyTo,
    SIZE_T size)
{
    BOOL isPeekSuccess = FALSE;

    m_pQueueLock->Lock();

    if ((FALSE == IsEmpty()) && (NULL != pCopyTo) && (size >= m_perSlotDataSize))
    {
        SlotDescriptor* pDesc = GetSlotDescriptor(m_head);

        Utils::Memcpy(pCopyTo, pDesc->pData, m_perSlotDataSize);

        isPeekSuccess = TRUE;
    }

    m_pQueueLock->Unlock();

    return isPeekSuccess;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::Release
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID HAL3Queue::Release(
    VOID* pData)
{
    CAMX_ASSERT(NULL != pData);

    if (NULL != pData)
    {
        m_pQueueLock->Lock();
        ReleaseCore(pData);
        m_pQueueLock->Unlock();
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::EnqueueWait
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult HAL3Queue::EnqueueWait(
    VOID* pData)
{
    CamxResult result = CamxResultEFailed;

    m_pQueueLock->Lock();

    CAMX_ASSERT(pData != NULL);

    if (pData != NULL)
    {
        do
        {
            while ((FALSE == m_cancelFullWait) && (FALSE == CanEnqueue()))
            {
                m_pWaitFull->Wait(m_pQueueLock->GetNativeHandle());
            }

            if (FALSE == m_cancelFullWait)
            {
                result = EnqueueCore(pData);
            }
            else
            {
                break;
            }
        } while (result != CamxResultSuccess);
    }

    m_pQueueLock->Unlock();

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::DequeueWait
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID* HAL3Queue::DequeueWait()
{
    VOID* pElement = NULL;

    m_pQueueLock->Lock();

    do
    {
        while ((FALSE == m_cancelEmptyWait) && (TRUE == IsEmpty()))
        {
            m_pWaitEmpty->Wait(m_pQueueLock->GetNativeHandle());
        }

        if (FALSE == m_cancelEmptyWait)
        {
            pElement = DequeueCore();
        }
        else
        {
            break;
        }
    } while (NULL == pElement);

    m_pQueueLock->Unlock();

    return pElement;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::WaitEmpty
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID HAL3Queue::WaitEmpty()
{
    m_pQueueLock->Lock();

    while (FALSE == IsEmpty())
    {
        m_pWaitFlush->Wait(m_pQueueLock->GetNativeHandle());
    }

    m_pQueueLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::CancelWait
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID HAL3Queue::CancelWait()
{
    m_pQueueLock->Lock();

    // First cancel all enqueue-s waiting
    m_cancelFullWait = TRUE;
    m_pWaitFull->Broadcast();

    // Then cancel all dequeue-s waiting
    m_cancelEmptyWait = TRUE;
    m_pWaitEmpty->Broadcast();

    m_pQueueLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::EnableWait
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID HAL3Queue::EnableWait()
{
    // Re-enable enqueue wait
    m_cancelFullWait = FALSE;

    // Re-enable dequeue wait
    m_cancelEmptyWait = FALSE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// HAL3Queue::IsSlotInUse
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL HAL3Queue::IsSlotInUse(
    UINT32 slotIndex
    ) const
{
    CAMX_ASSERT(slotIndex < m_maxSlots);

    return GetSlotDescriptor(slotIndex)->inUse;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HAL3Queue::DumpState
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID HAL3Queue::DumpState(
    INT     fd,
    UINT32  indent)
{
    CAMX_LOG_TO_FILE(fd, indent, "+---------------------------------+");
    CAMX_LOG_TO_FILE(fd, indent, "+ HAL3Queue:");

    /// @note Accessing with a TryLock since this is intended to be a post-mortem log.  If we try to enforce the lock, there's a
    ///       reasonable chance the post-mortem will deadlock. Failed locks will be noted.
    CamxResult result = m_pQueueLock->TryLock();

    if (CamxResultSuccess != result)
    {
        CAMX_LOG_TO_FILE(fd, indent + 3, "WARNING: Lock failed with status: %d.  Results may not be completely accurate",
            result);
    }

    if (FALSE == IsEmpty())
    {
        UINT32          headSlot    = m_head;
        SlotDescriptor* pDesc                           = GetSlotDescriptor(headSlot);
        // Total hack...Session is the only place that uses the HAL3Queue, and SessionCaptureRequest is the structure stored.
        // if more things use this, should store a callback function pointer for the structured dump on creation (or dump a
        // blob and post process the log).
        SessionCaptureRequest* pSessionRequest          = reinterpret_cast<SessionCaptureRequest*>(pDesc->pData);

        while (headSlot != m_tail)
        {
            CAMX_LOG_TO_FILE(fd, indent + 3, "Slot: %d  inUse: %d", pDesc->index, pDesc->inUse);

            for (UINT requestIndex = 0; requestIndex < pSessionRequest->numRequests; requestIndex++)
            {
                CaptureRequest* pReq = &(pSessionRequest->requests[requestIndex]);
                CAMX_LOG_TO_FILE(fd, indent + 4, "requestId: %lld  numBatchedFrames: %d  pipelineIndex: %d  isMultiRequest: %d",
                    pReq->requestId, pReq->numBatchedFrames, pReq->pipelineIndex, pReq->pMultiRequestData->isMultiRequest);

                for (UINT i = 0; i < pReq->numBatchedFrames; i++)
                {
                    CAMX_LOG_TO_FILE(fd, indent + 5, "streamBuffers[%d]", i);
                    CAMX_LOG_TO_FILE(fd, indent + 6, "sequenceId: %d  fwNumber: %lld",
                        pReq->streamBuffers[i].sequenceId, pReq->streamBuffers[i].originalFrameworkNumber);

                    for (UINT j = 0; j < pReq->streamBuffers[i].numOutputBuffers; j++)
                    {
                        CAMX_LOG_TO_FILE(fd, indent + 7, "outputBuffers[%d]", j);
                        CAMX_LOG_TO_FILE(fd, indent + 8, "stream: %p  buffer: %p (fd: %d)  buffer status: %d",
                            pReq->streamBuffers[i].outputBuffers[j].pStream,
                            pReq->streamBuffers[i].outputBuffers[j].phBuffer,
                            reinterpret_cast<NativeHandle*>(pReq->streamBuffers[i].outputBuffers[j].phBuffer)->data[0],
                            pReq->streamBuffers[i].outputBuffers[j].bufferStatus);
                        CAMX_LOG_TO_FILE(fd, indent + 8, "acquireFence: %d  releaseFence: %d",
                            pReq->streamBuffers[i].outputBuffers[j].acquireFence,
                            pReq->streamBuffers[i].outputBuffers[j].releaseFence);
                    }
                    for (UINT j = 0; j < pReq->streamBuffers[i].numInputBuffers; j++)
                    {
                        CAMX_LOG_TO_FILE(fd, indent + 7, "inputBufferInfo[%d]", j);
                        CAMX_LOG_TO_FILE(fd, indent + 8, "CSLFence: %d  port: %d",
                            pReq->streamBuffers[i].inputBufferInfo[j].fence,
                            pReq->streamBuffers[i].inputBufferInfo[j].portId);
                        CAMX_LOG_TO_FILE(fd, indent + 8, "stream: %p  buffer: %p (fd: %d)  buffer status: %d",
                            pReq->streamBuffers[i].inputBufferInfo[j].inputBuffer.pStream,
                            pReq->streamBuffers[i].inputBufferInfo[j].inputBuffer.phBuffer,
                            reinterpret_cast<NativeHandle*>(
                                pReq->streamBuffers[i].inputBufferInfo[j].inputBuffer.phBuffer)->data[0],
                            pReq->streamBuffers[i].inputBufferInfo[j].inputBuffer.bufferStatus);
                        CAMX_LOG_TO_FILE(fd, indent + 8, "acquireFence: %d  releaseFence: %d",
                            pReq->streamBuffers[i].inputBufferInfo[j].inputBuffer.acquireFence,
                            pReq->streamBuffers[i].inputBufferInfo[j].inputBuffer.releaseFence);
                    }
                    if (TRUE == pReq->pMultiRequestData->isMultiRequest)
                    {
                        for (UINT j = 0; j < MaxPipelinesPerSession; j++)
                        {
                            CAMX_LOG_TO_FILE(fd, indent + 7, "multiframe sync requestId:");
                            CAMX_LOG_TO_FILE(fd, indent + 8, "[%d] = %lld", j, pReq->pMultiRequestData->requestID[j]);
                        }
                    }
                }

            }

            headSlot = ((headSlot + 1) % m_maxSlots);
            pDesc = GetSlotDescriptor(headSlot);
        }
    }
    else
    {
        CAMX_LOG_TO_FILE(fd, indent + 3, "Queue is currently empty...m_head: %d  m_tail: %d", m_head, m_tail);
    }
    CAMX_LOG_TO_FILE(fd, indent, "+---------------------------------+");

    if (CamxResultSuccess == result)
    {
        m_pQueueLock->Unlock();
    }
}

CAMX_NAMESPACE_END
